07: Sub_Plugin - #7
Open
nikolaystrikhar wants to merge 4 commits into
Open
Conversation
- is_callable() was the type discriminator on every string-or-callable key, and it is true for any string naming an existing function. conflict_policy is designed to be readable from an option, so a stored 'date' or 'flush' was invoked instead of used: a TypeError at plugins_loaded on PHP 8, a silent empty policy on 7.4. Strings and bools are values now. - Type-check required keys. An array passed the truthiness check and cast to "Array", which every sub-plugin making that mistake would have shared as its registry key, activation key, and notice id. - Reject an uncallable dependency_check or activation_callback at registration. Read-time is_callable() made "not configured" and "configured but uncallable" indistinguishable, so a dependency_check pointing at a private method reported dependencies met and let the load run into the fatal it guards. - Drop the redundant network check: WordPress's is_plugin_active() already ORs it in. The test that pinned it described a state WordPress cannot produce. - Guard the plugin.php require on is_plugin_active_for_network, since is_plugin_active is a function third parties shim. - Give get_conflict_notice_message() a default, guard the filter result with is_scalar(), and pass the Sub_Plugin to every configured callable.
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What:
Sub_Plugin— one registered sub-plugin, its configuration, and every per-sub-plugin decision. The plan calls this the largest genuine review surface in the project, and the review bore that out.Stacked on #6.
Why this way: every predicate is a plain method taking no hooks, so the collaborators in later PRs stay thin and each decision is testable directly.
The review found six real defects in the design as planned. Each one is a config mistake a host could plausibly make, and in every case the old behaviour failed silently or destructively.
is_callable()was the type discriminator onconflict_policy,enabled, and both message keys. It returns true for any string naming an existing function — andconflict_policyis explicitly designed to be readable from an option (Conflict_Policy::is_valid()says so in its own docblock). A stored value ofdate,flush, orkeywas therefore invoked rather than used: an uncaughtTypeErroratplugins_loadedon PHP 8 — white screen on every request, including the dashboard you would use to fix it — and a silent empty policy on 7.4. A version-dependent split across the two legs CI actually tests. Strings and bools are values now; only real callables get called.An uncallable
dependency_checkreported dependencies met.is_callable( $check ) ? (bool) $check() : truecannot distinguish "not configured" from "configured but not invocable", and both answered true. Write[ $this, 'has_woocommerce' ]where the method is private, or typo a function name, and the check silently vanishes — the bundled plugin loads and fatals on the very dependency the key exists to guard, with no notice queued to explain it. Both callable-only keys are now validated at registration.Required keys were checked with
empty()but never type-checked. An array survives that and casts to the string"Array"— so every sub-plugin with the same mistake collapses onto one registry key, one activation-tracking key, and one notice id, overwriting each other. Now they must be non-empty strings.The network check was dead code. Verified against the WordPress install:
is_plugin_active()already ORs inis_plugin_active_for_network(). The OR cost a secondget_site_option()per sub-plugin per request, and the test defending it stubbedis_plugin_active => false, is_plugin_active_for_network => true— a state WordPress cannot produce. Both are fixed; that test now describes reality, and a new test asserts the basename actually reaches WordPress, which nothing checked before (three later PRs pass that same string todeactivate_plugins()).load_plugin_functions()guarded on the wrong function. Both live inwp-admin/includes/plugin.php, so it worked by coincidence — butif ( ! function_exists( 'is_plugin_active' ) )is a common third-party shim, and one would short-circuit the require and leaveis_plugin_active_for_network()undefined. It guards on the network function now.get_conflict_notice_message()had no fallback, and Task 14 has none either. A host that never configured a message would have been shown WordPress's raw "triggered a fatal error" screen — precisely what that rewrite exists to prevent. It takes a$defaultnow, and the plan is updated so Task 14 passes one.Also:
is_scalar()guards the filter result, since a filter returningWP_Errorwould have been a fatal on cast; every configured callable now receives theSub_Plugin, whichconflict_policydid andenableddid not; and the README documents theconflict_policyfilter (public API this PR introduces, previously undocumented), that the guard constant must be defined at file scope, and that the activation-request fatal is caught, not prevented.One plan defect hit while implementing: the fixture helper was named
make(), which collides withCodeception\Test\Unit::make(). Declaredprivate, that is a fatal at class-compile time — the suite does not fail, it fails to start. Renamed tomake_sub_plugin().Verify:
slic run unit— 77 tests, 111 assertions, green.composer test:analysis→[OK] No errors, exit 0.